/*******************************************************************
* MediaCoder - The universal audio/video transcoder
* Distributed under GPL license
* Copyright (c) 2005-06 Stanley Huang <stanleyhuangyc@yahoo.com.cn>
* All rights reserved.
*******************************************************************/
#include <windows.h>
#include <stdio.h>
#include "processwrapper.h"
#include "mplayerwrapper.h"

static CMPlayer* me = 0;

int thread_exit_flag = 0;

static int hex2int(char *p)
{
	register char c;
	register unsigned int i=0;

	if (p[1] == 'x' || p[1] == 'X') p += 2;
	if (!p) return 0;
	while (*p && (*p==' ' || *p=='\t')) p++;
	for(c=*p;;){
		if (c>='A' && c<='F')
			c-=7;
		else if (c>='a' && c<='f')
			c-=39;
		else if (c<'0' || c>'9')
			break;
		i=(i<<4)|(c&0xF);
		c=*(++p);
	}
	return (int)i;
}

DWORD WINAPI PlayThread(CMPlayer *player)
{
	bool eof = false;
	bool ready = false;
	bool error = false;
	int bytes = 0;

	thread_exit_flag = 0;
	do {
		char buf[1024];
		char *pbuf = buf + bytes;
		bytes = player->Read(&pbuf, sizeof(buf) - bytes);
		if (bytes <= 0) {
			player->proc.Cleanup();
			error = true;
			break;
		}

		char *s;
		char *p = buf;
		for (;;) {
			s = p;
			for (; *p && *p != '\r' && *p != '\n'; p++);
			if (!*p) break;
			*p = 0;
			while (*(++p) == '\n' || *p == '\r');
			if (strstr(s, "Exiting...")) {
				player->proc.Cleanup();
				error = true;
				break;
			}
			if (strstr(s, "Video: no video")) {
				player->hasVideo = false;
				ready = true;
				break;
			}
			if (strstr(s, "Audio: no sound"))
				player->hasAudio = false;

			if ((player->hasVideo && !strncmp(s, "VO: ", 4))) {
				ready = true;
				break;
			} else if (!strncmp(s, "ID_", 3)) {
				char *id = s + 3;
				char *v = strchr(id, '=');
				if (v) {
					*(v++) = 0;
					player->ParseInfo(id, v);
				}
			}
		}
		bytes = strlen(s);
		if (bytes)
			memcpy(buf + bytes, s, bytes + 1);
	} while (!ready && !error);

	if (error) return 0;

	player->SetState(PLAYER_PLAYING);

	while (player->state != PLAYER_STOPPING) {
		if (player->state == PLAYER_PLAYING) {
			if (player->proc.Wait() || player->GetPosData() < 0) {
				eof = true;
				break;
			}
		}
		{
#if 1
			DWORD bytes;
			HANDLE hStdout=GetStdHandle(STD_OUTPUT_HANDLE);
			WriteFile(hStdout, player->console, strlen(player->console), &bytes, 0);
#endif
		}
		player->WaitEvent(250);
	}
	if (eof) player->SetState(PLAYER_STOPPING);
	player->SetState(PLAYER_IDLE);
	player->proc.Cleanup();
	player->pos = -1;
	player->hWnd = 0;

	thread_exit_flag = 1;

	return 0;
}

CMPlayer::CMPlayer(char* path):callback(0),state(PLAYER_IDLE),pos(-1),duration(0),playingfile(0)
{
	hMutexQuery = CreateMutex(0, 0, 0);
	hEventState = CreateEvent(0, 0, 0, 0);
	memset(&audio, 0, sizeof(audio));
	memset(&video, 0, sizeof(video));
	console = (char*)malloc(CONSOLE_BUF_SIZE);
	mppath = _strdup(path);
}

CMPlayer::~CMPlayer()
{
	free(console);
	free(mppath);
	if (playingfile) free(playingfile);
	CloseHandle(hMutexQuery);
	CloseHandle(hEventState);
}

void CMPlayer::ParseInfo(char* id, char* v)
{
	if (!strcmp(id, "LENGTH")) {
		duration = (int)(atof(v) * 1000);
	} else if (!strcmp(id, "VIDEO_FORMAT")) {
		video.format = hex2int(v + 2);
	} else if (!strcmp(id, "VIDEO_ID")) {
		video.id = atoi(v);
	} else if (!strcmp(id, "VIDEO_BITRATE")) {
		video.bitrate = atoi(v);
	} else if (!strcmp(id, "VIDEO_WIDTH")) {
		video.width = atoi(v);
	} else if (!strcmp(id, "VIDEO_HEIGHT")) {
		video.height = atoi(v);
	} else if (!strcmp(id, "VIDEO_FPS")) {
		video.fps = (float)atof(v);
	} else if (!strcmp(id, "VIDEO_ASPECT")) {
		video.aspect = (float)atof(v);
	} else if (!strcmp(id, "VIDEO_CODEC")) {
		if (v[0] == 'f' && v[1] == 'f') v += 2;
		strncpy(video.codec, v, sizeof(video.codec));
	} else if (!strcmp(id, "AUDIO_ID")) {
		audio.id = atoi(v);
	} else if (!strcmp(id, "AUDIO_FORMAT")) {
		audio.format = atoi(v);
	} else if (!strcmp(id, "AUDIO_BITRATE")) {
		audio.bitrate = atoi(v);
	} else if (!strcmp(id, "AUDIO_RATE")) {
		audio.samplerate = atoi(v);
	} else if (!strcmp(id, "AUDIO_NCH")) {
		audio.channels = atoi(v);
	} else if (!strcmp(id, "AUDIO_CODEC")) {
		if (v[0] == 'f' && v[1] == 'f') v += 2;
		strncpy(audio.codec, v, sizeof(audio.codec));
	}
}

int CMPlayer::DoCallback(int eventid, void* arg)
{
	if (callback) {
		return (*callback)(eventid, arg);
	} else {
		return 0;
	}
}

int CMPlayer::WaitEvent(int timeout)
{
	return WaitForSingleObject(hEventState, timeout);
}

void CMPlayer::SetState(PLAYER_STATES newstate)
{
	if (state != newstate) {
		state = newstate;
		DoCallback(PC_STATE_CHANGE, (void*)newstate);
	}
}

bool CMPlayer::Command(char* cmd, char** result)
{
	WaitForSingleObject(hMutexQuery, INFINITE);
	bool success = false;
	int ret;
	int cmdlen = strlen(cmd) + 1;
	char* cmdline = (char*)malloc(cmdlen + 1);
	sprintf(cmdline, "%s\n", cmd);
	console[0] = 0;
	while(1) {
		if (proc.Write(cmdline, cmdlen) == cmdlen) {
			if (result) {
				char *p = 0;
				Sleep(100);
				if(strncmp(cmdline, "volume", 6) == 0)
					break;
				ret = Read(&p, 0);
				if (ret > 0) {
					p = strrchr(p, '=');
					if (p)
						p++;
					else
						p = console;
				} else {
					p = console;
				}
				*result = p;
			}
			success = true;
		}
		break;
	}
	ReleaseMutex(hMutexQuery);
	return success;
}

int CMPlayer::Read(char** pbuf, int bufsize)
{
	int bytes = -1;
	char *buf = console;
	int interval = 10;
	if (pbuf) {
		if (*pbuf)
			buf = *pbuf;
		else
			*pbuf = buf;
	}
	if (!bufsize) bufsize = CONSOLE_BUF_SIZE;
	bytes = proc.Read(buf, bufsize - 1);
	if (bytes >= 0) {
		buf[bytes] = 0;
		DoCallback(PC_CONSOLE_OUTPUT, buf);
	}
	return bytes;
}

void CMPlayer::Stop()
{
	if (state == PLAYER_PLAYING || state == PLAYER_PAUSED) {
		SetState(PLAYER_STOPPING);
		Command("quit");
		SetEvent(hEventState);
		WaitForSingleObject(hThread, 500);
		Sleep(200);
		proc.Cleanup();
	}
}

bool CMPlayer::Seek(int seekpos, int type)
{
	char buf[32];
	char *p = buf;
	if (type == 1) {	 // 1 is seek to <value> % IN the movie
		p += sprintf(buf, "seek %d 1", seekpos, type);
	} 
	else if(type == 0) { // 0 is relative seek of +/- <value> seconds 
		p += sprintf(buf, "seek +");
	}
	else {				 //2 is seek to an absolute position of <value> seconds
#if 0
		p += sprintf(buf, "seek %.3f %d", (float)seekpos / 1000, type);
#else
		p += sprintf(buf, "seek %d %d", seekpos, type);
#endif
	}
	if (state == PLAYER_PAUSED) {
		sprintf(p, "\npause");
	}
	if (Command(buf)) {
		GetPosData();
		return true;
	} else {
		return false;
	}
}

int CMPlayer::GetPos()
{
	return pos;
}

int CMPlayer::GetPosData()
{
	char* result = 0;
	if (Command("get_time_pos", &result)) {
		int n = (int)(atof(result) * 1000);
#if 0		
		if (n >= 0 && n <= duration) pos = n;
#else
		if (n >= 0) pos = n;
#endif

		return pos;
	} else {
		return -1;
	}
}

void CMPlayer::RefreshVideo()
{
	Command("refresh_vo");
}

int CMPlayer::Pause()
{
	if (Command("pause")) {
		if (state == PLAYER_PAUSED) {
			SetState(PLAYER_PLAYING);
			return PLAYER_PLAYING;
		} else {
			SetState(PLAYER_PAUSED);
			return PLAYER_PAUSED;
		}
	} else {
		return -1;
	}
}

int CMPlayer::Step()
{
	if (Command("step")) {
		return 0;
	} else {
		return -1;
	}
}

bool CMPlayer::Play(char* filename, HWND hWndParent, char* options)
{
	char cmd[1024];
	bool success;
	char *p;

	if (state == PLAYER_PLAYING) {
		Stop();
		Sleep(500);
	}
	pos = 0;
	if (playingfile) free(playingfile);
	playingfile = _strdup(filename);


	/*
	if (!strncmp(filename, "http://", 7) || !strncmp(filename, "ftp://", 6)) {
		char *p = pref.GetValueStr("overall.generic.proxy");
		if (*p) p = strstr(p, "://");
		if (p && *p) {
			sprintf(file, "http_proxy://%s/", p + 3);
		}
		strcat(file, filename);
	} else {
		sprintf(file, "\"%s\"", filename);
	}
	*/
	p = cmd + sprintf(cmd, "\"%s\" \"%s\"  -slave -quiet -nokeepaspect -colorkey %d ", mppath, filename, 0x10);

	if (hWndParent) {
		p += sprintf(p, " -wid %d", hWndParent);
	}
	if (options) {
		strcat(cmd, options);
	}

	proc.flags = SF_REDIRECT_STDIN | SF_REDIRECT_STDOUT;
	
	DoCallback(PC_CONSOLE_OUTPUT, cmd);

	if (!strncmp(filename, "mf://", 5)) {
		char *curdir = _strdup(filename + 5);
		char *p = strrchr(curdir, '\\');
		if (!p) {
			success = proc.Spawn(cmd);
		} else {
			*p = 0;
			success = proc.Spawn(cmd, curdir);
		}
		free(curdir);
	} else {
		success = proc.Spawn(cmd);
	}
	if (!success) return false;

	proc.SetPriority(HIGH_PRIORITY_CLASS);

	hasAudio = true;
	hasVideo = true;
	memset(&audio, 0, sizeof(audio));
	memset(&video, 0, sizeof(video));

	DWORD id;
	state = PLAYER_IDLE;
	hThread = CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)PlayThread,(void*)this,0,&id);
	for (int i = 0; i < 50 && state != PLAYER_PLAYING && WaitForSingleObject(hThread, 100) == WAIT_TIMEOUT; i++);
	if (state == PLAYER_PLAYING) {
		if (hWndParent) {
			EnumChildWindows(hWndParent, EnumWindowCallBack, (LPARAM)this);
			hWnd = hWndParent;
		} else {
			EnumWindows(EnumWindowCallBack, (LPARAM)this);
		}
		return true;
	}
	TerminateThread(hThread, 0);
	proc.Cleanup();
	return false;
}

void CMPlayer::SetFS(bool fs)
{
	Command(fs ? "vo_fullscreen 1" : "vo_fullscreen 0");
}

bool CMPlayer::GetFS()
{
	char* result = 0;
	Command("get_vo_fullscreen", &result);
	return *result == '1';
}

BOOL CALLBACK CMPlayer::EnumWindowCallBack(HWND hwnd, LPARAM lParam)
{
	((CMPlayer*)lParam)->hWnd = hwnd;
	return FALSE; 
}